package nsfs

import (
	"GLibHac/crypto"
	"GLibHac/fssystem"
	"fmt"
	"github.com/pkg/errors"
)

type TicketSigType int

func (t TicketSigType) String() string {
	switch t {
	case TicketSigTypeRsa4096Sha1:
		return "Rsa4096Sha1"
	case TicketSigTypeRsa2048Sha1:
		return "Rsa2048Sha1"
	case TicketSigTypeEcdsaSha1:
		return "EcdsaSha1"
	case TicketSigTypeRsa4096Sha256:
		return "Rsa4096Sha256"
	case TicketSigTypeRsa2048Sha256:
		return "Rsa2048Sha256"
	case TicketSigTypeEcdsaSha256:
		return "EcdsaSha256"
	default:
		return fmt.Sprintf("TicketSigType(%d)", t)
	}
}

const (
	TicketSigTypeRsa4096Sha1   = TicketSigType(0x10000)
	TicketSigTypeRsa2048Sha1   = TicketSigType(0x10001)
	TicketSigTypeEcdsaSha1     = TicketSigType(0x10002)
	TicketSigTypeRsa4096Sha256 = TicketSigType(0x10003)
	TicketSigTypeRsa2048Sha256 = TicketSigType(0x10004)
	TicketSigTypeEcdsaSha256   = TicketSigType(0x10005)
)

type TitleKeyType uint8

func (t TitleKeyType) String() string {
	switch t {
	case TitleKeyTypeCommon:
		return "Common"
	case TitleKeyTypePersonalized:
		return "Personalized"
	default:
		return fmt.Sprintf("TitleKeyType(%d)", t)
	}
}

const (
	TitleKeyTypeCommon       = TitleKeyType(iota)
	TitleKeyTypePersonalized = TitleKeyType(iota)
)

type Ticket struct {
	SignatureType    TicketSigType
	Signature        []byte
	Issuer           string
	TitleKeyBlock    []byte
	formatVersion    byte
	Type             TitleKeyType
	TicketVersion    uint16
	LicenseType      byte
	CryptoType       byte
	PropertyMask     uint32
	TicketId         uint64
	DeviceId         uint64
	RightsId         []byte
	AccountId        uint32
	SectTotalSize    int32
	SectHeaderOffset int32
	SectNum          int16
	SectEntrySize    int16
	File             []byte
}

func (t *Ticket) GetTitleKey(keySet *KeySet) ([]byte, error) {
	if t.Type == TitleKeyTypeCommon {
		commonKey := make([]byte, 0x10)
		copy(commonKey, t.TitleKeyBlock)
		return commonKey, nil
	}
	return crypto.DecryptRsaOaep(t.TitleKeyBlock, keySet.ETicketExtKeyRsa)
}

func NewTicket(reader fssystem.IStorage) (ticket *Ticket, err error) {
	ticket = &Ticket{}
	fileStart := reader.Position()
	if v, err := reader.ReadInt(); err != nil {
		return nil, err
	} else {
		ticket.SignatureType = TicketSigType(v)
	}
	readLength := 0
	skipLength := 0
	switch ticket.SignatureType {
	case TicketSigTypeRsa4096Sha1, TicketSigTypeRsa4096Sha256:
		readLength = 0x200
		skipLength = 0x3c
	case TicketSigTypeRsa2048Sha1, TicketSigTypeRsa2048Sha256:
		readLength = 0x100
		skipLength = 0x3c
	case TicketSigTypeEcdsaSha1, TicketSigTypeEcdsaSha256:
		readLength = 0x3c
		skipLength = 0x40
	default:
		return nil, errors.Errorf("unknown signature type: %d", ticket.SignatureType)
	}

	if ticket.Signature, err = reader.ReadBytes(readLength); err != nil {
		return
	}
	if _, err = reader.ReadBytes(skipLength); err != nil {
		return
	}

	dataStart := reader.Position()
	if ticket.Issuer, err = reader.ReadUtf8Z(0x40); err != nil {
		return
	}
	if err = reader.SetPosition(dataStart + 0x40); err != nil {
		return
	}
	if ticket.TitleKeyBlock, err = reader.ReadBytes(0x100); err != nil {
		return
	}
	if ticket.formatVersion, err = reader.ReadByte(); err != nil {
		return
	}
	if v, err := reader.ReadByte(); err != nil {
		return nil, err
	} else {
		ticket.Type = TitleKeyType(v)
	}
	if ticket.TicketVersion, err = reader.ReadUint16(); err != nil {
		return
	}
	ticket.LicenseType, err = reader.ReadByte()
	if err != nil {
		return
	}
	ticket.CryptoType, err = reader.ReadByte()
	if err != nil {
		return
	}
	ticket.PropertyMask, err = reader.ReadUint32()
	err = reader.SetPosition(dataStart + 0x150)
	if err != nil {
		return
	}
	ticket.TicketId, err = reader.ReadUint64()
	if err != nil {
		return
	}
	ticket.DeviceId, err = reader.ReadUint64()
	if err != nil {
		return
	}
	ticket.RightsId, err = reader.ReadBytes(0x10)
	if err != nil {
		return
	}
	ticket.AccountId, err = reader.ReadUint32()
	if err != nil {
		return
	}
	ticket.SectTotalSize, err = reader.ReadInt()
	if err != nil {
		return
	}
	ticket.SectHeaderOffset, err = reader.ReadInt()
	if err != nil {
		return
	}
	ticket.SectNum, err = reader.ReadInt16()
	if err != nil {
		return
	}
	ticket.SectEntrySize, err = reader.ReadInt16()
	if err != nil {
		return
	}
	err = reader.SetPosition(fileStart)
	if err != nil {
		return
	}
	ticket.File, err = reader.ReadBytes(int(ticket.SectHeaderOffset))
	return
}
